
package com.smfx.yxt.excel;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.commons.beanutils.DynaClass;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.util.Region;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.smfx.yxt.excel.tags.ITag;

@SuppressWarnings("unchecked")
public class ExcelParser {
	
	private static Logger logger = LoggerFactory.getLogger(ExcelParser.class);

	public static final String EXPR_OPEN = "${";

	public static final String EXPR_CLOSE = "}";

	public static final String KEY_TAG = "#";

	public static Map<String, String> tagPackageMap = new HashMap<String, String>();

	private static Map tagMap = new HashMap();

	static {
		tagPackageMap.put(ITag.class.getPackage().getName(), ITag.class.getPackage().getName());
	}

	/**
	 * parse the Excel template
	 * 
	 * @param sheet Excel sheet
	 * @param fromRow the start
	 * @param toRow the end
	 * @return int skip number
	 */
	public static int parse(HSSFSheet sheet, int fromRow, int toRow) {
		int[] shift = new int[] { 0, 0, 0 }; // {SkipNum, ShiftNum, break flag}
		int shiftCount = 0;

		int rownum = fromRow;
		while (rownum <= toRow) {
			// shift
			rownum += shift[1] + shift[0];
			toRow += shift[1];
			if (rownum > toRow)
				break;

			shift[0] = 0;
			shift[1] = 0;
			shift[2] = 0;
			HSSFRow row = sheet.getRow(rownum);
			// set current row number
			ExcelUtils.addValue("currentRowNo", new Integer(rownum + 1));
			if (null == row) {
				rownum++;
				continue;
			}

			for (short colnum = row.getFirstCellNum(); colnum <= row.getLastCellNum(); colnum++) {
				HSSFCell cell = row.getCell(colnum);
				if (null == cell) {
					continue;
				}
				if (cell.getCellType() != HSSFCell.CELL_TYPE_STRING) {
					continue;
				}
				// if the cell is null then continue
				String cellstr = cell.getRichStringCellValue().getString();
				if (null == cellstr || "".equals(cellstr)) {
					continue;
				}

				ITag tag = getTagClass(cellstr);
				if (null != tag) {
					shift = tag.parseTag(sheet, row, cell);
				}
				else {
					parseCell(sheet, row, cell);
				}

				shiftCount += shift[1];
				if (shift[2] == 1)
					break;
			}
			rownum++;
		}
		return shiftCount;
	}

	/**
	 * get a instance by the tag name.
	 * 
	 * @param str tag name
	 * @return ITag instance
	 */
	public static ITag getTagClass(String str) {
		String tagName = "";
		int keytag = str.indexOf(KEY_TAG);
		if (keytag < 0)
			return null;
		if (!(keytag < str.length() - 1))
			return null;
		String tagRight = str.substring(keytag + 1, str.length());
		if (tagRight.startsWith(KEY_TAG))
			return null;

		str = str.substring(str.indexOf(KEY_TAG) + KEY_TAG.length(), str.length());
		StringTokenizer st = new StringTokenizer(str, " ");
		if (st.hasMoreTokens()) {
			tagName = st.nextToken();
		}
		tagName = tagName.substring(0, 1).toUpperCase() + tagName.substring(1, tagName.length());
		tagName += "Tag";

		// find in tagMap first, if not exist, search in the package
		ITag tag = (ITag) tagMap.get(tagName);
		if (tag == null) {
			Iterator tagPackages = tagPackageMap.values().iterator();
			// seach the tag class
			while (tagPackages.hasNext() && null == tag) {
				String packageName = (String) tagPackages.next();
				try {
					Class clazz = Class.forName(packageName + "." + tagName);
					tag = (ITag) clazz.newInstance();
				}
				catch (Exception e) {
					tag = null;
				}
			}
			if (tag != null) {
				// find it, cache it
				tagMap.put(tagName, tag);
			}
		}
		return tag;
	}

	/**
	 * get the value from context by the expression
	 * 
	 * @param expr
	 * @return Object the value of the expr
	 */
	public static Object getValue(String expr) {
		return ExcelUtils.getValue(expr);
	}

	/**
	 * parse the expression ${model[${index}]} only one ${} and startWith ${ endWith }
	 * 
	 * @param expr
	 * @return the value of the expr
	 */
	public static Object parseExpr(String expr) {
		int sPos = expr.indexOf(EXPR_OPEN);
		if (sPos < 0) {
			return expr;
		}
		else {
			int ePos = expr.indexOf(EXPR_CLOSE, sPos + EXPR_OPEN.length());
			if (ePos < 0) {
				return expr;
			}
			if (sPos == 0 && ePos + EXPR_CLOSE.length() == expr.length()) {
				return ExcelUtils.getValue(expr.substring(sPos + EXPR_OPEN.length(), ePos));
			}

			StringBuffer result = new StringBuffer(100);
			result.append(expr.substring(0, sPos))//
					.append(ExcelUtils.getValue(expr.substring(sPos + EXPR_OPEN.length(), ePos)));
			int oldPos = ePos;
			while ((sPos = expr.indexOf(EXPR_OPEN, ePos + EXPR_CLOSE.length())) != -1) {
				result.append(expr.substring(ePos + EXPR_CLOSE.length(), sPos));
				ePos = expr.indexOf(EXPR_CLOSE, sPos + EXPR_OPEN.length());
				if (ePos == -1) {
					ePos = oldPos;
					break;
				}
				result.append(ExcelUtils.getValue(expr.substring(sPos + EXPR_OPEN.length(), ePos)));
			}
			result.append(expr.substring(ePos + EXPR_CLOSE.length()));
			return result.toString();
		}
	}

	/**
	 * parse the cell
	 * 
	 * @param cell excel cell
	 */
	public static void parseCell(HSSFSheet sheet, HSSFRow row, HSSFCell cell) {

		String str = cell.getRichStringCellValue().getString();
		if (null == str || "".equals(str)) {
			return;
		}

		if (str.indexOf(EXPR_OPEN) < 0)
			return;

		boolean bJustExpr = str.length() == (str.length() - str.lastIndexOf(EXPR_OPEN));
		boolean bMerge = "!".equals(str.substring(str.indexOf(EXPR_OPEN) + EXPR_OPEN.length(), str.indexOf(EXPR_OPEN)
				+ EXPR_OPEN.length() + 1));

		if (str.indexOf(EXPR_OPEN) < 0)
			return;
		if (bMerge) {
			str = EXPR_OPEN + str.substring(EXPR_OPEN.length() + 1);
		}
		Object value = parseExpr(str);

		// replace the cell
		if (null != value) {
			if (bJustExpr && "java.lang.Integer".equals(value.getClass().getName())) {
				cell.setCellValue(Double.parseDouble(value.toString()));
			}
			else if (bJustExpr && "java.lang.Double".equals(value.getClass().getName())) {
				cell.setCellValue(((Double) value).doubleValue());
			}
			else if (bJustExpr && "java.util.Date".equals(value.getClass().getName())) {
				cell.setCellValue((Date) value);
			}
			else if (bJustExpr && "java.lang.Boolean".equals(value.getClass().getName())) {
				cell.setCellValue(((Boolean) value).booleanValue());
			}
			else {
				cell.setCellValue(new HSSFRichTextString(value.toString()));
			}
		}
		else {
			cell.setCellValue(new HSSFRichTextString());
		}

		// merge the cell that has a "!" character at the expression
		if (row.getRowNum() - 1 >= sheet.getFirstRowNum() && bMerge) {
			HSSFRow lastRow = WorkbookUtils.getRow(row.getRowNum() - 1, sheet);
			HSSFCell lastCell = WorkbookUtils.getCell(lastRow, cell.getCellNum());
			boolean canMerge = false;
			if (lastCell.getCellType() == cell.getCellType()) {
				switch (cell.getCellType()) {
				case HSSFCell.CELL_TYPE_STRING:
					canMerge = lastCell.getRichStringCellValue().equals(cell.getRichStringCellValue());
					break;
				case HSSFCell.CELL_TYPE_BOOLEAN:
					canMerge = lastCell.getBooleanCellValue() == cell.getBooleanCellValue();
					break;
				case HSSFCell.CELL_TYPE_NUMERIC:
					canMerge = lastCell.getNumericCellValue() == cell.getNumericCellValue();
					break;
				}
			}
			if (canMerge) {
				Region region = new Region(lastRow.getRowNum(), lastCell.getCellNum(), row.getRowNum(), cell.getCellNum());
				sheet.addMergedRegion(region);
			}
		}

	}

	/**
	 * get properties of the JavaBean
	 * 
	 * @param clazz JavaBean
	 * @return fields in the javabean
	 */
	public static Field[] getBeanProperties(Class clazz) {
		Field[] fields = clazz.getDeclaredFields();
		Method[] methods = clazz.getMethods();
		String m = "";

		for (int i = 0; i < methods.length; i++) {
			m += methods[i].getName() + ",";
		}

		List flist = new ArrayList();
		for (int i = 0; i < fields.length; i++) {
			if (m.indexOf("get" + fields[i].getName().substring(0, 1).toUpperCase()
					+ fields[i].getName().substring(1, fields[i].getName().length())) >= 0) {
				flist.add(fields[i]);
			}
		}
		Field[] result = new Field[flist.size()];
		flist.toArray(result);
		return result;
	}

	public static DynaProperty[] getBeanProperties(DynaClass clazz) {
		DynaProperty dynaProperties[] = clazz.getDynaProperties();
		return dynaProperties;
	}

	/**
	 * get Iterator from the object
	 * 
	 * @param collection
	 * @return Iterator of the object
	 */
	public static Iterator getIterator(Object collection) {
		Iterator iterator = null;
		if (collection.getClass().isArray()) {
			try {
				// If we're lucky, it is an array of objects
				// that we can iterate over with no copying
				iterator = Arrays.asList((Object[]) collection).iterator();
			}
			catch (ClassCastException e) {
				// Rats -- it is an array of primitives
				int length = Array.getLength(collection);
				ArrayList c = new ArrayList(length);
				for (int i = 0; i < length; i++) {
					c.add(Array.get(collection, i));
				}
				iterator = c.iterator();
			}
		}
		else if (collection instanceof Collection) {
			iterator = ((Collection) collection).iterator();
		}
		else if (collection instanceof Iterator) {
			iterator = (Iterator) collection;
		}
		else if (collection instanceof Map) {
			iterator = ((Map) collection).entrySet().iterator();
		}
		return iterator;
	}

	/**
	 * get Skip Num
	 * 
	 * @param tagstart
	 * @param tagend
	 * @return skip number
	 */
	public static int getSkipNum(int tagstart, int tagend) {
		return tagend - tagstart;
	}

	/**
	 * get shift Num
	 * 
	 * @param old_tagend
	 * @param tagstart
	 * @return shift number
	 */
	public static int getShiftNum(int old_tagend, int tagstart) {
		return tagstart - old_tagend - 1;
	}
}
